using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
using XLib;

public class SoundManager : MonoSingleton<SoundManager>
{
    private Dictionary<string, AudioClip> loadClips = new Dictionary<string, AudioClip>();
    private Queue<AudioSource> stopSounds = new Queue<AudioSource>();
    private List<AssetRequest> clipRequest = new List<AssetRequest>();

    private AudioSource playingBGM;
    private List<AudioSource> playingSounds = new List<AudioSource>();

    private float bgmVolume = 1f;
    private bool bgmMute = false;

    private float soundVolume = 1f;
    private bool soundMute = false;

    private const string SOUND_BGM_VOLUME = "SOUND_BGM_VOLUME";
    private const string SOUND_BGM_MUTE = "SOUND_BGM_MUTE";

    private const string SOUND_EFFECT_VOLUME = "SOUND_EFFECT_VOLUME";
    private const string SOUND_EFFECT_MUTE = "SOUND_EFFECT_MUTE";

    private void Awake()
    {
        bgmVolume = PlayerPrefs.GetFloat(SOUND_BGM_VOLUME, 1f);
        bgmMute = PlayerPrefs.GetInt(SOUND_BGM_MUTE, 0) == 1;

        soundVolume = PlayerPrefs.GetFloat(SOUND_EFFECT_VOLUME, 1f);
        soundMute = PlayerPrefs.GetInt(SOUND_EFFECT_MUTE, 0) == 1;
    }

    public void AddPreload(string[] audioPaths)
    {
        foreach(var audioPath in audioPaths)
        {
            var request = Assets.LoadAssetAsync(audioPath, typeof(AudioClip));
            request.onComplete += OnLoadClipFinish;
            clipRequest.Add(request);
        }
    }

    public float GetPreloadProgress()
    {
        var progress = 0f;
        foreach(var request in clipRequest)
        {
            progress += request.progress;
        }
        progress = progress / clipRequest.Count;
        return progress;
    }

    public void CleanUp()
    {
        foreach(var request in clipRequest)
        {
            request.Release();
        }
        clipRequest.Clear();
        loadClips.Clear();
    }

    private void OnLoadClipFinish(AssetRequest request)
    {
        loadClips[request.assetPath] = request.asset as AudioClip;
    }

    public void SetBGMVolume(float volume)
    {
        bgmVolume = volume;
        PlayerPrefs.SetFloat(SOUND_BGM_VOLUME, volume);
        if (playingBGM != null)
            playingBGM.volume = volume;
    }

    public float GetBGMVolume()
    {
        return bgmVolume;
    }

    public void SetBGMMute(bool mute)
    {
        bgmMute = mute;
        PlayerPrefs.SetInt(SOUND_BGM_MUTE, mute ? 1 : 0);
        if (playingBGM != null)
            playingBGM.mute = mute;
    }

    public bool IsBGMMute()
    {
        return bgmMute;
    }

    public void SetSoundVolume(float volume)
    {
        soundVolume = volume;
        PlayerPrefs.SetFloat(SOUND_EFFECT_VOLUME, volume);
        foreach(var audioSource in playingSounds)
        {
            audioSource.volume = volume;
        }
    }

    public float GetSoundVolume()
    {
        return soundVolume;   
    }

    public void SetSoundMute(bool mute)
    {
        soundMute = mute;
        PlayerPrefs.SetInt(SOUND_EFFECT_MUTE, mute ? 1 : 0);
        foreach(var audioSource in playingSounds)
        {
            audioSource.mute = mute;
        }
    }

    public bool IsSoundMute()
    {
        return soundMute;
    }

    public void PlayBGM(string audioPath, bool bAsync)
    {
        if (playingBGM != null)
            GameObject.Destroy(playingBGM);
        playingBGM = LoadAndPlayClip(audioPath, 0f, bAsync, true, bgmVolume);
        playingBGM.mute = bgmMute;
    }

    public void PlayUISound(string audioPath, bool bAsync)
    {
        var audioSource = LoadAndPlayClip(audioPath, 0f, bAsync, false, soundVolume);
        audioSource.mute = soundMute;
        playingSounds.Add(audioSource);
    }

    public void Play3DSound(string audioPath, bool bAsync)
    {
        var audioSource = LoadAndPlayClip(audioPath, 1f, bAsync, false, soundVolume);
        audioSource.mute = soundMute;
        playingSounds.Add(audioSource);
    }

    private AudioSource LoadAndPlayClip(string audioPath, float spatialBlend, bool bAsync, bool loop, float volume)
    {
        var audioSource = GetOrCreateAudioSource(spatialBlend, volume);
        audioSource.name = Path.GetFileNameWithoutExtension(audioPath);
    
        if (loadClips.TryGetValue(audioPath, out var clip))
        {
            PlayClip(audioSource, clip, loop);
            return audioSource;
        }

        if (!bAsync)
        {
            var request = Assets.LoadAsset(audioPath, typeof(AudioClip));
            clip = request.asset as AudioClip;
            PlayClip(audioSource, clip, loop);
            loadClips[audioPath] = clip;
            clipRequest.Add(request);
        }
        else
        {
            var request = Assets.LoadAssetAsync(audioPath, typeof(AudioClip));
            request.onComplete += (AssetRequest request) => {
                var clip = request.asset as AudioClip;
                PlayClip(audioSource, clip, loop);
                loadClips[audioPath] = clip;
            };
            clipRequest.Add(request);
        }
        return audioSource;
    }

    private void PlayClip(AudioSource audioSource, AudioClip audioClip, bool loop)
    {
        audioSource.loop = loop;
        audioSource.clip = audioClip;
        // audioSource.mute = false;
        audioSource.Play();

        if (!loop)
        {
            TimerManager.AddTimer(()=> {
                audioSource.gameObject.SetActive(false);
                playingSounds.Remove(audioSource);
                stopSounds.Enqueue(audioSource);
            }, 0, audioSource.clip.length, 1);
        }
    }

    private AudioSource GetOrCreateAudioSource(float spatialBlend, float volume = 1f)
    {
        var audioSource = stopSounds.Count > 0 ? stopSounds.Dequeue() : null;
        if (audioSource == null)
        {
            var audioObject = new GameObject("SoundItem", new Type[] { typeof(AudioSource) });
            audioObject.TryGetComponent<AudioSource>(out audioSource);
            audioObject.transform.parent = transform;
        }
        audioSource.gameObject.SetActive(true);

        audioSource.spatialBlend = spatialBlend;
        audioSource.volume = volume;
        
        return audioSource;
    }
}
